{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.下载movielens 1M数据，根据打分构造样本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy\n",
    "import tensorflow as tf\n",
    "import os\n",
    "import random\n",
    "from collections import defaultdict\n",
    "\n",
    "def load_data(data_path):\n",
    "    #\n",
    "    train_data = defaultdict(list)\n",
    "    test_data = defaultdict(list)\n",
    "    watch_list = defaultdict(set)\n",
    "    max_uid = -1\n",
    "    max_item = -1\n",
    "    \n",
    "    with open(data_path, 'r') as f:\n",
    "        for line in f.readlines():\n",
    "            u, i, r, t = map(int,line.strip().split(\"::\"))\n",
    "            watch_list[u].add(i)\n",
    "            #每个用户的评分都存在dict\n",
    "            if len(test_data[u]) == 0:\n",
    "                test_data[u].append((i,r))\n",
    "            elif len(test_data[u]) == 1 and test_data[u][0][1] != r:\n",
    "                test_data[u].append((i,r))\n",
    "            else:\n",
    "                train_data[u].append((i,r))\n",
    "            if u > max_uid:\n",
    "                max_uid = u\n",
    "            if i > max_item:\n",
    "                max_item = i\n",
    "    return train_data,test_data,watch_list,max_uid,max_item"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['movies.dat', 'ratings.dat', 'users.dat', 'README']\n"
     ]
    }
   ],
   "source": [
    "print(os.listdir('/tmp/BPR/ml-1m'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>1193</td>\n",
       "      <td>5</td>\n",
       "      <td>978300760</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>661</td>\n",
       "      <td>3</td>\n",
       "      <td>978302109</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>914</td>\n",
       "      <td>3</td>\n",
       "      <td>978301968</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>3408</td>\n",
       "      <td>4</td>\n",
       "      <td>978300275</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>2355</td>\n",
       "      <td>5</td>\n",
       "      <td>978824291</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   0     1  2          3\n",
       "0  1  1193  5  978300760\n",
       "1  1   661  3  978302109\n",
       "2  1   914  3  978301968\n",
       "3  1  3408  4  978300275\n",
       "4  1  2355  5  978824291"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "data_rating = pd.read_csv('/tmp/BPR/ml-1m/ratings.dat', header=None, sep='::', engine='python')\n",
    "data_rating.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1000209, 4)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_rating.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6040 6040 6040 3952\n",
      "[(914, 3), (3408, 4), (2355, 5), (1197, 3), (1287, 5), (2804, 5), (594, 4), (919, 4), (595, 5), (938, 4), (2398, 4), (2918, 4), (1035, 5), (2791, 4), (2687, 3), (2018, 4), (3105, 5), (2797, 4), (2321, 3), (720, 3), (1270, 5), (527, 5), (2340, 3), (48, 5), (1097, 4), (1721, 4), (1545, 4), (745, 3), (2294, 4), (3186, 4), (1566, 4), (588, 4), (1907, 4), (783, 4), (1836, 5), (1022, 5), (2762, 4), (150, 5), (1, 5), (1961, 5), (1962, 4), (2692, 4), (260, 4), (1028, 5), (1029, 5), (1207, 4), (2028, 5), (531, 4), (3114, 4), (608, 4), (1246, 4)]\n",
      "[(1193, 5), (661, 3)]\n"
     ]
    }
   ],
   "source": [
    "train_data, test_data, watch_list, user_count, item_count = load_data('/tmp/BPR/ml-1m/ratings.dat')\n",
    "print(len(train_data), len(test_data), user_count, item_count)\n",
    "print(train_data[1])\n",
    "print(test_data[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.随机采样，生成batch训练数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[5354  260 1097]\n",
      " [5317 2110 2065]\n",
      " [1827 3911  111]\n",
      " [1500 2797 1380]\n",
      " [ 295 2723 2395]]\n"
     ]
    }
   ],
   "source": [
    "def generate_train_batch(rating_data, batch_size=256):\n",
    "    t = []\n",
    "    for b in range(batch_size):\n",
    "        u = random.sample(rating_data.keys(), 1)[0]\n",
    "        i,r1 = random.sample(rating_data[u], 1)[0]\n",
    "        j,r2 = random.sample(rating_data[u], 1)[0] \n",
    "        #直到选到一个打分不同的pair\n",
    "        while r1 == r2:\n",
    "            u = random.sample(rating_data.keys(), 1)[0]\n",
    "            i,r1 = random.sample(rating_data[u], 1)[0]\n",
    "            j,r2 = random.sample(rating_data[u], 1)[0]\n",
    "        #i>j\n",
    "        if r1 > r2:\n",
    "            t.append([u, i, j])\n",
    "        else:\n",
    "            t.append([u, j, i])\n",
    "    return numpy.asarray(t)\n",
    "batch_data = generate_train_batch(train_data)\n",
    "print(batch_data[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[   1 1193  661]\n",
      " [   2 1357 3068]\n",
      " [   3 3421 1641]\n",
      " [   4 3468 1210]\n",
      " [   5 1175 2987]]\n"
     ]
    }
   ],
   "source": [
    "def generate_test_batch(rating_data):\n",
    "    t = []\n",
    "    for u in rating_data:\n",
    "        i,r1 = rating_data[u][0]\n",
    "        j,r2 = rating_data[u][1]\n",
    "        if r1 > r2:\n",
    "            t.append([u, i, j])\n",
    "        else:\n",
    "            t.append([u, j, i])\n",
    "    return numpy.asarray(t)\n",
    "batch_data = generate_test_batch(test_data)\n",
    "print(batch_data[:5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3.定义网络结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "regulation_rate = 0.01\n",
    "bias_reg = 0.01\n",
    "\n",
    "def bpr_mf(user_count, item_count, hidden_dim):\n",
    "    u = tf.placeholder(tf.int32, [None])\n",
    "    i = tf.placeholder(tf.int32, [None])\n",
    "    j = tf.placeholder(tf.int32, [None])\n",
    "\n",
    "    with tf.device(\"/cpu:0\"):\n",
    "        # Uv*Iv + Ub +Ib\n",
    "        user_vec = tf.get_variable(\"user_vec\", [user_count+1, hidden_dim], \n",
    "                            initializer=tf.random_normal_initializer(0, 0.1))\n",
    "        item_vec = tf.get_variable(\"item_vec\", [item_count+1, hidden_dim], \n",
    "                                initializer=tf.random_normal_initializer(0, 0.1))\n",
    "        item_bias = tf.get_variable(\"item_bias\", [item_count+1, 1], initializer=tf.random_normal_initializer(0, 0.1))    #item bias\n",
    "        # \n",
    "        u_vec = tf.nn.embedding_lookup(user_vec, u)\n",
    "        i_vec = tf.nn.embedding_lookup(item_vec, i)\n",
    "        j_vec = tf.nn.embedding_lookup(item_vec, j)  \n",
    "        #\n",
    "        i_bias = tf.nn.embedding_lookup(item_bias, i)       \n",
    "        j_bias = tf.nn.embedding_lookup(item_bias, j)  \n",
    "        #\n",
    "        xui = i_bias + tf.reduce_sum(tf.multiply(u_vec, i_vec), 1, keep_dims=True)\n",
    "        xuj = j_bias + tf.reduce_sum(tf.multiply(u_vec, j_vec), 1, keep_dims=True)\n",
    "        xuij = xui-xuj\n",
    "        #i+  j+ i>j\n",
    "        auc = tf.reduce_mean(tf.to_float(xuij > 0))\n",
    "        # norm\n",
    "        l2_norm = tf.add_n([\n",
    "              regulation_rate * tf.reduce_sum(tf.multiply(u_vec, u_vec)),\n",
    "              regulation_rate * tf.reduce_sum(tf.multiply(i_vec, i_vec)),\n",
    "              regulation_rate * tf.reduce_sum(tf.multiply(j_vec, j_vec)),\n",
    "              bias_reg * tf.reduce_sum(tf.multiply(i_bias, i_bias)),\n",
    "              bias_reg * tf.reduce_sum(tf.multiply(j_bias, j_bias)),\n",
    "          ]) \n",
    "        #auc = tf.reduce_mean(tf.to_float(xuij > 0))\n",
    "        bprloss =  l2_norm - tf.reduce_mean(tf.log(tf.sigmoid(xuij))) \n",
    "        global_step = tf.Variable(0, trainable=False)\n",
    "        train_op =  tf.train.AdamOptimizer().minimize(bprloss, global_step=global_step)  \n",
    "    return u, i, j,auc, bprloss, train_op\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4.batch训练评估效果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /srv/conda/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Colocations handled automatically by placer.\n",
      "WARNING:tensorflow:From <ipython-input-17-2c9348a7d3bf>:24: calling reduce_sum_v1 (from tensorflow.python.ops.math_ops) with keep_dims is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "keep_dims is deprecated, use keepdims instead\n",
      "WARNING:tensorflow:From <ipython-input-17-2c9348a7d3bf>:28: to_float (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.cast instead.\n",
      "WARNING:tensorflow:From /srv/conda/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.cast instead.\n",
      "epoch:  100\n",
      "train_loss:  2.6241978907585146\n",
      "train_auc:  0.511875\n",
      "test_loss:  37.138245\n",
      "test_auc:  0.538245\n",
      "epoch:  200\n",
      "train_loss:  1.821675226688385\n",
      "train_auc:  0.538828125\n",
      "test_loss:  24.197048\n",
      "test_auc:  0.56109273\n",
      "epoch:  300\n",
      "train_loss:  1.4065587544441223\n",
      "train_auc:  0.57078125\n",
      "test_loss:  16.76308\n",
      "test_auc:  0.5781457\n",
      "epoch:  400\n",
      "train_loss:  1.1679750323295592\n",
      "train_auc:  0.6087890625\n",
      "test_loss:  12.111695\n",
      "test_auc:  0.6016556\n",
      "epoch:  500\n",
      "train_loss:  1.019121465086937\n",
      "train_auc:  0.63546875\n",
      "test_loss:  9.235834\n",
      "test_auc:  0.61456954\n",
      "epoch:  600\n",
      "train_loss:  0.9257429808378219\n",
      "train_auc:  0.6482421875\n",
      "test_loss:  7.2182345\n",
      "test_auc:  0.6246689\n",
      "epoch:  700\n",
      "train_loss:  0.8598821604251862\n",
      "train_auc:  0.6554296875\n",
      "test_loss:  5.8075395\n",
      "test_auc:  0.619702\n",
      "epoch:  800\n",
      "train_loss:  0.8140952491760254\n",
      "train_auc:  0.663046875\n",
      "test_loss:  4.726012\n",
      "test_auc:  0.6370861\n",
      "epoch:  900\n",
      "train_loss:  0.7808300232887269\n",
      "train_auc:  0.6673046875\n",
      "test_loss:  4.034007\n",
      "test_auc:  0.6461921\n",
      "epoch:  1000\n",
      "train_loss:  0.7576049846410752\n",
      "train_auc:  0.6715625\n",
      "test_loss:  3.4913397\n",
      "test_auc:  0.655298\n"
     ]
    }
   ],
   "source": [
    "with tf.Graph().as_default(), tf.Session() as session:\n",
    "    u, i, j, auc, bprloss, train_op = bpr_mf(user_count, item_count, 32)\n",
    "    tf.global_variables_initializer().run()\n",
    "    test_batch_data = generate_test_batch(test_data)\n",
    "    _batch_bprloss = 0\n",
    "    _batch_auc = 0\n",
    "    for epoch in range(1, 1001):\n",
    "        #\n",
    "        batch_data = generate_train_batch(train_data)\n",
    "        _auc,_bprloss, _train_op = session.run([auc, bprloss, train_op], \n",
    "                                feed_dict={u:batch_data[:,0], i:batch_data[:,1], j:batch_data[:,2]})\n",
    "        _batch_bprloss += _bprloss\n",
    "        _batch_auc += _auc\n",
    "        \n",
    "        if epoch%100 == 0:\n",
    "            print (\"epoch: \", epoch)\n",
    "            print (\"train_loss: \", _batch_bprloss / 100)\n",
    "            print (\"train_auc: \", _batch_auc / 100)\n",
    "            _batch_bprloss = 0\n",
    "            _batch_auc = 0\n",
    "            #\n",
    "            _auc, _bprloss = session.run([auc, bprloss],\n",
    "                                    feed_dict={u:test_batch_data[:,0], i:test_batch_data[:,1], j:test_batch_data[:,2]}\n",
    "                                )\n",
    "            \n",
    "            print(\"test_loss: \",_bprloss)\n",
    "            print(\"test_auc: \",_auc)\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
